﻿
namespace PhotoAlbum.Web.Services
{
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Data;
    using System.Diagnostics.Contracts;
    using System.IO;
    using System.Linq;
    using System.ServiceModel.DomainServices.EntityFramework;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;
    using System.Web;
    using PhotoAlbum.Web;

    [EnableClientAccess()]
    public class PicturesService : PhotoAlbumService
    {
        public static readonly string UserImagesFolderName = "img";
        public static readonly string PngContentType = "image/png";
        public static readonly string PngExtension = "png";
        public static readonly string JpegContentType = "image/jpeg";
        public static readonly string JpegExtension = "jpeg";

        private enum ContentType
        {
            Jpeg,
            Png
        }

        [Query(HasSideEffects = false)]
        public IQueryable<Picture> GetPicturesForAlbumId(int albumId)
        {
            var album = this.ObjectContext.Albums.FirstOrDefault(a => a.Id == albumId);

            if (album == null)
            {
                throw new Exception("Album with the given id does not exist.");
            }

            if (this.CanUserViewAlbum(album))
            {
                album.ViewsCount++;
                this.ObjectContext.SaveChanges();
                return album.Pictures.CreateSourceQuery().OrderByDescending(a => a.DateCreated);
            }
            else
            {
                throw new Exception("The current user cannot access this album.");
            }
        }

        [Query(HasSideEffects = false)]
        public IQueryable<Picture> GetPicturesForPictureId(int pictureId)
        {
            var picture =  this.GetViewablePicture(pictureId);
            return picture.Album.Pictures.CreateSourceQuery();
        }

        [Query(HasSideEffects = false, IsDefault = true, ResultLimit = 20)]
        public IQueryable<Picture> GetLatestPublicPhotos()
        {
            return this.ObjectContext
                            .Pictures
                            .Where(p => p.Album.VisibilityStore == (int)AlbumVisibility.Public)
                            .OrderByDescending(p => p.DateCreated);
        }

        [Query(HasSideEffects = false, ResultLimit = 40)]
        public IQueryable<Picture> GetLatestPublicPhotosByTagId(int tagId)
        {
            return this.GetLatestPublicPhotos().Where(p => p.Tags.Any(t => t.Id == tagId));
        }

        [Query(HasSideEffects = false, ResultLimit = 30)]
        public IQueryable<Picture> GetPicturesForUsername([Required] string username)
        {
            if (this.PhotoUser != null
                && this.PhotoUser.UserName.Equals(username))
            {
                this.PhotoUser.PrivateAlbums.Load();
                return this.PhotoUser
                                .Albums
                                .CreateSourceQuery()
                                .SelectMany(a => a.Pictures)
                                .OrderByDescending(p => p.DateCreated);
            }
            else
            {
                return this.ObjectContext
                                .Pictures
                                .Where(p => p.Album.VisibilityStore == (int)AlbumVisibility.Public
                                    && p.Album.Owner.UserName == username)
                                .OrderByDescending(p => p.DateCreated);
            }
        }

        [RequiresAuthentication]
        public void InsertPicture(Picture picture)
        {
            Contract.Requires(picture.Image != null);
            Contract.Requires(picture.Album != null);
            Contract.Requires(picture.Title != null);

            picture.DateCreated = DateTime.Now;

            var album = this.ObjectContext.Albums.FirstOrDefault(a => a.Id == picture.AlbumId);
              if (album == null)
            {
                throw new Exception("An image can only be added to an existing album.");
            }
            if (album.OwnerId != PhotoUser.Id)
            {
                throw new Exception("Cannot update pictures that are not owned by you");
            }
            if (picture.Image != null
                || picture.Thumbnail != null)
            {
                throw new Exception("The relative image paths cannot be set by the client.");
            }

            if (picture.PictureUpdate != null)
            {
                this.SavePictureAndThumbnail(picture);
            }

            if ((picture.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(picture, EntityState.Added);
            }
            else
            {
                this.ObjectContext.Pictures.AddObject(picture);
            }
        }

        private void SavePictureAndThumbnail(Picture picture)
        {
            if (picture.PictureUpdate == null)
            {
                return;
            }
            try
            {
                var root = HttpContext.Current.Server.MapPath("~");
                if (picture.PictureUpdate.Image != null)
                {
                    var relativeImagePath = Path.Combine(UserImagesFolderName, PhotoUser.UserName, Path.GetRandomFileName());
                    relativeImagePath = this.AssignExtensionFromContentType(relativeImagePath, picture.ContentType);
                    var localImagePath = Path.Combine(root, relativeImagePath);
                    this.SaveImageFile(localImagePath, picture.PictureUpdate.Image);
                    this.TryDeleteOldFile(picture.Image);
                    picture.Image = relativeImagePath;
                }

                if (picture.PictureUpdate.Thumbnail != null)
                {
                    var relativeThumbPath = Path.Combine(UserImagesFolderName, PhotoUser.UserName, Path.GetRandomFileName());
                    relativeThumbPath = this.AssignExtensionFromContentType(relativeThumbPath, picture.ContentType);
                    var localThumbPath = Path.Combine(root, relativeThumbPath);
                    this.SaveImageFile(localThumbPath, picture.PictureUpdate.Thumbnail);
                    this.TryDeleteOldFile(picture.Thumbnail);
                    picture.Thumbnail = relativeThumbPath;
                }
            }
            catch (Exception exception)
            {
                throw new Exception("The image could not be saved on the server.", exception);
            }
        }

        private string AssignExtensionFromContentType(string relativeImagePath, string contentTypeString)
        {
            var contentType = ContentType.Jpeg;
            if (PngContentType.Equals(contentTypeString))
            {
                contentType = ContentType.Png;
            }
            return Path.ChangeExtension(relativeImagePath, contentType == ContentType.Png ? PngExtension : JpegExtension);
        }

        private void TryDeleteOldFile(string relativePath)
        {
            if (!string.IsNullOrEmpty(relativePath))
            {
                var localPath = HttpContext.Current.Server.MapPath(relativePath);
                try
                {
                    if(File.Exists(localPath))
                    {
                        File.Delete(localPath);
                    }
                }
                catch
                {
                    // Not deleting a picture is not a huge problem.
                }
            }
        }

        private string SaveImageFile(string localImagePath, byte[] imageContent)
        {
            using (var file = File.Create(localImagePath))
            {
                file.Write(imageContent, 0, imageContent.Length);
            }
            return localImagePath;
        }

        [RequiresAuthentication]
        public void InsertPictureData(PictureData pictureData)
        {
            // Picture data will only be inserted with a new picture.
        }

        [RequiresAuthentication]
        public void UpdatePicture(Picture currentPicture)
        {
            var original = this.ChangeSet.GetOriginal(currentPicture);
            original.AlbumReference.Load();
            if(original.Album.OwnerId != this.PhotoUser.Id)
            {
                throw new Exception("Cannot update a picture unless you are its owner.");
            }
            if(original.AlbumId != currentPicture.AlbumId)
            {
                currentPicture.AlbumReference.Load();
                if(currentPicture.Album.OwnerId != this.PhotoUser.Id)
                {
                    throw new Exception("Cannot move a picture to an album owned by someone else.");
                }
            }
            if (original.Image != currentPicture.Image
                || original.Thumbnail != currentPicture.Thumbnail)
            {
                throw new Exception("The relative paths of the images cannot be latered from the client.");
            }
            this.ObjectContext.Pictures.AttachAsModified(currentPicture, original);
        }

        [RequiresAuthentication]
        public void DeletePicture(Picture picture)
        {
            var original = this.ChangeSet.GetOriginal(picture);
            original.AlbumReference.Load();

            if (original.Album.OwnerId != this.PhotoUser.Id)
            {
                throw new Exception("Cannot delete a picture owned by someone else.");
            }

            if ((picture.EntityState == EntityState.Detached))
            {
                this.ObjectContext.Pictures.Attach(picture);
            }
            this.ObjectContext.Pictures.DeleteObject(picture);
        }

        [Invoke(HasSideEffects = true)]
        [RequiresAuthentication]
        public void UpdatePictureData([Required] PictureData pictureData)
        {
            var picture = this.ObjectContext.Pictures.FirstOrDefault(p => p.Id == pictureData.PictureId);
            if (picture == null)
            {
                throw new Exception("Picture with the given id was not found");
            }

            var album = picture.Album;
            if (album.Owner.Id != this.PhotoUser.Id)
            {
                throw new Exception("Only pictures owned by you can be updated.");
            }
            picture.PictureUpdate = pictureData;
            this.SavePictureAndThumbnail(picture);
            ObjectContext.SaveChanges();
        }

        public IQueryable<PictureData> ExposeData()
        {
            throw new NotImplementedException("This method is not implemented.");
        }

        [RequiresAuthentication]
        [Query(IsDefault = false, HasSideEffects = false)]
        public IQueryable<Picture> GetFavoritePictures()
        {
            return this.PhotoUser.FavoritePictures.CreateSourceQuery().OrderBy(p => p.DateCreated);
        }

        [Invoke(HasSideEffects = true)]
        public void AddPictureIdToFavorites(int pictureId)
        {
            this.PhotoUser.FavoritePictures.Load();
            var picture = this.GetViewablePicture(pictureId);
            this.PhotoUser.FavoritePictures.Load();
            // No need to fail if it is already favorite.
            if (!this.PhotoUser.FavoritePictures.Contains(picture))
            {
                this.PhotoUser.FavoritePictures.Add(picture);
                this.ObjectContext.SaveChanges();
            }
        }

        [Invoke(HasSideEffects = false)]
        public bool IsPictureFavorite(int pictureId)
        {
            this.PhotoUser.FavoritePictures.Load();

            // No exception on misson picture here, no need to be so strict.
            return this.PhotoUser.FavoritePictures.Any(p => p.Id == pictureId);
        }

        [Invoke(HasSideEffects = true)]
        public void RemovePictureIdFromFavorites(int pictureId)
        {
            this.PhotoUser.FavoritePictures.Load();
            var picture = this.PhotoUser.FavoritePictures.FirstOrDefault(p => p.Id == pictureId);
            if (picture == null)
            {
                throw new Exception("Only a picture which is a favorite can be removd from favorites.");
            }
            PhotoUser.FavoritePictures.Remove(picture);
            this.ObjectContext.SaveChanges();
        }
    }
}


